/*
 * OpenThe9Box (OT9B): Java(TM) API for NeufBox REST interface.
 * Copyright (C) 2015 ScalAgent D.T.
 * Copyright (C) 2015 Turbotel
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA.
 *
 * Initial developer(s): Freyssinet Andre (ScalAgent D.T.)
 * Contributor(s): 
 */
package org.turbotel.ot9b;

import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;

import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.HttpClientBuilder;
import org.turbotel.ot9b.beans.Auth;
import org.turbotel.ot9b.beans.DNS;
import org.turbotel.ot9b.beans.DSLInfo;
import org.turbotel.ot9b.beans.Error;
import org.turbotel.ot9b.beans.Host;
import org.turbotel.ot9b.beans.LanInfo;
import org.turbotel.ot9b.beans.PPPInfo;
import org.turbotel.ot9b.beans.Response;
import org.turbotel.ot9b.beans.SysInfo;
import org.turbotel.ot9b.beans.WLanInfo;
import org.turbotel.ot9b.beans.WanInfo;
import org.turbotel.ot9b.tools.Hash;
import org.turbotel.ot9b.tools.Log;

/**
 * Java wrapper for the 9Box.
 * 
 * @see http://dev.efixo.net/doc/api-rest.html
 * 
 * @author freyssin
 */
public class NBWrapper {
  private static final String URL_API_REST = "/api/1.0/?method=";
  
  // Authentification methods
  private static final String GET_TOKEN = "auth.getToken";
  private static final String CHECK_TOKEN = "auth.checkToken";

  // Backup3g methods
  private static final String B3G_FORCE_DATA = "backup3g.forceDataLink";
  private static final String B3G_FORCE_VOIP = "backup3g.forceVoipLink";
  private static final String B3G_GET_PIN = "backup3g.getPinCode";
  private static final String B3G_SET_PIN = "backup3g.setPinCode";
  
  // DDNS methods
  private static final String DDNS_ENABLE = "ddns.enable";
  private static final String DDNS_DISABLE = "ddns.disable";
  private static final String DDNS_UPDATE = "ddns.forceUpdate";
  private static final String DDNS_INFO = "ddns.getInfo";
  private static final String DDNS_SERVICE = "ddns.setService";

  // DSL methods
  private static final String DSL_INFO = "dsl.getInfo";
  
  // Firewall methods
  private static final String FW_ENABLE = "firewall.enableSmtpFilter";
  private static final String FW_DISABLE = "firewall.disableSmtpFilter";
  private static final String FW_INFO = "firewall.getInfo";
  
  // Hotspot methods
  private static final String HOTSPOT_ENABLE = "hotspot.enable";
  private static final String HOTSPOT_DISABLE = "hotspot.disable";
  private static final String HOTSPOT_START = "hotspot.start";
  private static final String HOTSPOT_RESTART = "hotspot.restart";
  private static final String HOTSPOT_STOP = "hotspot.stop";
  private static final String HOTSPOT_INFO = "hotspot.getInfo";
  private static final String HOTSPOT_SET_MODE = "hotspot.setMode";
  
  // LAN methods
  private static final String LAN_ADD_DNS = "lan.addDnsHost";
  private static final String LAN_DEL_DNS = "lan.deleteDnsHost";
  private static final String LAN_GET_DNS = "lan.getDnsHostList";
  private static final String LAN_GET_HOSTS = "lan.getHostsList";
  private static final String LAN_INFO = "lan.getInfo";
  
  // ONT methods
  private static final String ONT_INFO = "ont.getInfo";
  
  // P910ND methods
  private static final String P910ND_INFO = "p910nd.getInfo";
  
  // PPP methods
  private static final String PPP_GET_CREDENTIALS = "ppp.getCredentials";
  private static final String PPP_SET_CREDENTIALS = "ppp.setCredentials";
  private static final String PPP_INFO = "ppp.getInfo";
  
  // SMB methods
  private static final String SMB_INFO = "smb.getInfo";
  
  // System method
  private static final String SYS_INFO = "system.getInfo";
  private static final String SYS_GET_WPAKEY = "system.getWpaKey";
  private static final String SYS_REBOOT = "system.reboot";
  private static final String SYS_SET_MODE = "system.setNetMode";
  private static final String SYS_SET_CLIENT = "system.setRefClient";
  
  // VOIP methods
  private static final String VOIP_INFO = "voip.getInfo";
  private static final String VOIP_HISTORY = "voip.getCallhistoryList";
  private static final String VOIP_START = "voip.start";
  private static final String VOIP_RESTART = "voip.restart";
  private static final String VOIP_STOP = "voip.stop";
  
  // WAN methods
  private static final String WAN_INFO = "wan.getInfo";
  
  // WLAN methods
  private static final String WLAN_INFO = "wlan.getInfo";
  private static final String WLAN_ENABLE = "wlan.enable";
  private static final String WLAN_DISABLE = "wlan.disable";
  private static final String WLAN_START = "wlan.start";
  private static final String WLAN_RESTART = "wlan.restart";
  private static final String WLAN_STOP = "wlan.stop";
  private static final String WLAN_SET_CHANNEL = "wlan.setChannel";
  private static final String WLAN_SET_WL0ENC = "wlan.setWl0Enc";
  private static final String WLAN_SET_WL0ENCTYPE = "wlan.setWl0Enctype";
  private static final String WLAN_SET_WL0KEYTYPE = "wlan.setWl0Keytype";
  private static final String WLAN_SET_WL0SSID = "wlan.setWl0Ssid";
  private static final String WLAN_SET_WL0WEPKEY = "wlan.setWl0Wepkey";
  private static final String WLAN_SET_MODE = "wlan.setWlanMode";
  
  private String ip;
  private String login; 
  private String password;

  private HttpClient httpClient;
  
  public NBWrapper(String ip, String login, String password) {
    this.ip = ip;
    this.login = login;
    this.password = password;
    
    // create HTTP Client
    httpClient = HttpClientBuilder.create().build();
  }

  /**
   * Generic method providing the REST GET interaction with specified NB
   * 
   * @param uri The URI request
   * @return    The response object
   * 
   * @throws NetworkException There is an error during HTTP REST request.
   * @throws MarshalException There is an error during the REST reply unmarshalling.
   */
  private Response getResponse(String uri) throws NetworkException, MarshalException {
    HttpGet request = new HttpGet(uri); 
    // Add additional header to request which accepts application/xml data
    request.addHeader("accept", "application/xml");

    HttpResponse response = null;
    try {
      response = httpClient.execute(request);
    } catch (Exception e) {
      throw new NetworkException("Cannot request the 9box", e);
    }
    if (response.getStatusLine().getStatusCode() != 200) {
      throw new NetworkException("Failed : HTTP error code : " + response.getStatusLine().getStatusCode());
    }

    Response resp = null;
    try {
      // TODO: Factorizes this code in constructor?
      JAXBContext context = JAXBContext.newInstance(Response.class);
      Unmarshaller un = context.createUnmarshaller();

      resp = (Response) un.unmarshal(response.getEntity().getContent());
    } catch (Exception e) {
      throw new MarshalException("Cannot marshal the 9box reply", e);
    }
    return resp;
  }

  /**
   * Generic method providing the REST POST interaction with specified NB
   * 
   * @param uri The URI request
   * @return    The response object
   * 
   * @throws NetworkException There is an error during HTTP REST request.
   * @throws MarshalException There is an error during the REST reply unmarshalling.
   */
  private Response postRequest(String uri) throws NetworkException, MarshalException {
    HttpPost request = new HttpPost(uri); 
    // Add additional header to request which accepts application/xml data
    request.addHeader("accept", "application/xml");

    HttpResponse response = null;
    try {
      response = httpClient.execute(request);
    } catch (Exception e) {
      throw new NetworkException("Cannot request the 9box", e);
    }
    if (response.getStatusLine().getStatusCode() != 200) {
      throw new NetworkException("Failed : HTTP error code : " + response.getStatusLine().getStatusCode());
    }

    Response resp = null;
    try {
      JAXBContext context = JAXBContext.newInstance(Response.class);
      Unmarshaller un = context.createUnmarshaller();

      resp = (Response) un.unmarshal(response.getEntity().getContent());
    } catch (Exception e) {
      throw new MarshalException("Cannot marshal the 9box reply", e);
    }
    
    return resp;
  }
  
  // ************************************************************
  // ********** auth section
  // ************************************************************
  
  /**
   * Returns a new authentication token.
   * This method is publicly available.
   * 
   * @return a new authentication token or null if there is an error.
   * 
   * @throws NetworkException There is an error during HTTP REST request.
   * @throws MarshalException There is an error during the REST reply unmarshalling.
   * @throws APIException     There is an error returned by 9box API.
   */
  private Auth getToken() throws NetworkException, MarshalException, APIException {
    String uri = "http://" + ip + URL_API_REST + GET_TOKEN;

    Response resp = null;
    try {
      resp = getResponse(uri);
      if (Log.debug) Log.d(GET_TOKEN + " -> " + resp.getAuth());
    } catch (NetworkException|MarshalException exc) {
      Log.e("Cannot get response from " + GET_TOKEN + " request", exc);
      throw exc;
    }

    if (resp.getStat().equals("ok"))
      return resp.getAuth();

    Error err = resp.getErr();
    Log.e("failed " + GET_TOKEN + " request: (" + err.getCode() + ") " + err.getMsg());
    throw new APIException(GET_TOKEN + " request fails: " + err.getMsg(), err.getCode());
  }

  /**
   * Validates an authentication token.
   * This method is publicly available.

   * @param token the token to validate.
   * @return the validated token if success, null otherwise.
   * 
   * @throws NetworkException There is an error during HTTP REST request.
   * @throws MarshalException There is an error during the REST reply unmarshalling.
   * @throws APIException     There is an error returned by 9box API.
   */
  private Auth checkToken(String token) throws NetworkException, MarshalException, APIException {
    String loginHash = null;
    String passwordHash = null;

    try {
      loginHash = Hash.hashSHA256(login);
      loginHash = Hash.hashHmacSHA256(token, loginHash);

      passwordHash = Hash.hashSHA256(password);
      passwordHash = Hash.hashHmacSHA256(token, passwordHash);
    } catch (Exception exc) {
      Log.e("Issue with Hash in checkToken", exc);
      throw new APIException("Encryption issue", 0);
    }

    String uri = "http://" + ip + URL_API_REST + CHECK_TOKEN + "&token=" + token + "&hash=" + loginHash + passwordHash;

    Response resp = null;
    try {
      resp = getResponse(uri);
      if (Log.debug) Log.d(CHECK_TOKEN + " -> " + resp.getAuth());
    } catch (NetworkException|MarshalException exc) {
      Log.e("Cannot get response from " + CHECK_TOKEN + " request", exc);
      throw exc;
    }

    if (resp.getStat().equals("ok"))
      return resp.getAuth();

    Error err = resp.getErr();
    Log.e("failed " + CHECK_TOKEN + " request: (" + err.getCode() + ") " + err.getMsg());
    throw new APIException(CHECK_TOKEN + " request fails: " + err.getMsg(), err.getCode());
  }

  // The auth object containing the valid token for current session.
  private Auth auth;
  
  /**
   * Method allowing the authentication for the current session (needed to call private methods).
   * This method uses login and password informations set at object creation.
   * 
   * @throws NetworkException There is an error during HTTP REST request.
   * @throws MarshalException There is an error during the REST reply unmarshalling.
   * @throws APIException     There is an error returned by 9box API.
   */
  public void authenticate() throws NetworkException, MarshalException, APIException {
    auth = getToken();
    if (auth != null)
      auth = checkToken(auth.getToken());
  }

  // ************************************************************
  // ********** backup3g section
  // ************************************************************

  // ************************************************************
  // ********** ddns section
  // ************************************************************

  // ************************************************************
  // ********** dsl section
  // ************************************************************
  
  /**
   * Returns informations about DSL connection.
   * 
   * @return informations about DSL connection.
   * 
   * @throws NetworkException There is an error during HTTP REST request.
   * @throws MarshalException There is an error during the REST reply unmarshalling.
   * @throws APIException     There is an error returned by 9box API.
   */
  public DSLInfo getDslInfo() throws NetworkException, MarshalException, APIException {
    String uri = "http://" + ip + URL_API_REST + DSL_INFO + "&token=" + auth.getToken();

    Response resp = null;
    try {
      resp = getResponse(uri);
      if (Log.debug) Log.d(DSL_INFO + " -> " + resp.getDsl());
    } catch (NetworkException|MarshalException exc) {
      Log.e("Cannot get response from " + DSL_INFO + " request", exc);
      throw exc;
    }

    if (resp.getStat().equals("ok"))
      return resp.getDsl();
    
    Error err = resp.getErr();
    Log.e("failed " + DSL_INFO + " request: (" + err.getCode() + ") " + err.getMsg());
    throw new APIException(DSL_INFO + " request fails: " + err.getMsg(), err.getCode());
  }

  // ************************************************************
  // ********** firewall section
  // ************************************************************

  // ************************************************************
  // ********** hotspot section
  // ************************************************************

  // ************************************************************
  // ********** lan section
  // ************************************************************

  /**
   * Get the list of DNS entries on the LAN.
   * This method is publicly available.
   * 
   * @return list  of DNS entries on the LAN.
   * 
   * @throws NetworkException There is an error during HTTP REST request.
   * @throws MarshalException There is an error during the REST reply unmarshalling.
   * @throws APIException     There is an error returned by 9box API.
   */
  public List<DNS> getDnsHostList() throws NetworkException, MarshalException, APIException {
    String uri = "http://" + ip + URL_API_REST + LAN_GET_DNS + ((auth != null)?"&token=" + auth.getToken():"");
    
    Response resp = null;
    try {
      resp = getResponse(uri);
      if (Log.debug) Log.d(LAN_GET_DNS + " -> " + resp.getDNS());
    } catch (NetworkException|MarshalException exc) {
      Log.e("Cannot get response from " + LAN_GET_DNS + " request", exc);
      throw exc;
    }

    if (resp.getStat().equals("ok"))
      return resp.getDNS();
    
    Error err = resp.getErr();
    Log.e("failed " + LAN_GET_DNS + " request: (" + err.getCode() + ") " + err.getMsg());
    throw new APIException(LAN_GET_DNS + " request fails: " + err.getMsg(), err.getCode());
  }
  
  /**
   * Get the list of hosts on the LAN.
   * This method is publicly available.
   * 
   * @return the list of hosts on the LAN.
   * 
   * @throws NetworkException There is an error during HTTP REST request.
   * @throws MarshalException There is an error during the REST reply unmarshalling.
   * @throws APIException     There is an error returned by 9box API.
   */
  public List<Host> getHostsList() throws NetworkException, MarshalException, APIException {
    String uri = "http://" + ip + URL_API_REST + LAN_GET_HOSTS + ((auth != null)?"&token=" + auth.getToken():"");
    
    Response resp = null;
    try {
      resp = getResponse(uri);
      if (Log.debug) Log.d(LAN_GET_HOSTS + " -> " + resp.getHost());
    } catch (NetworkException|MarshalException exc) {
      Log.e("Cannot get response from " + LAN_GET_HOSTS + " request", exc);
      throw exc;
    }

    if (resp.getStat().equals("ok"))
      return resp.getHost();
    
    Error err = resp.getErr();
    Log.e("failed " + LAN_GET_HOSTS + " request: (" + err.getCode() + ") " + err.getMsg());
    throw new APIException(LAN_GET_HOSTS + " request fails: " + err.getMsg(), err.getCode());
  }
  
  /**
   * Returns LAN informations: box IP address, netmask and DHCP configuration.
   * This method is publicly available since firmware 2.1.2.
   * 
   * @return LAN informations.
   * 
   * @throws NetworkException There is an error during HTTP REST request.
   * @throws MarshalException There is an error during the REST reply unmarshalling.
   * @throws APIException     There is an error returned by 9box API.
   */
  public LanInfo getLanInfo() throws NetworkException, MarshalException, APIException {
    String uri = "http://" + ip + URL_API_REST + LAN_INFO + ((auth != null)?"&token=" + auth.getToken():"");
    
    Response resp = null;
    try {
      resp = getResponse(uri);
      if (Log.debug) Log.d(LAN_INFO + " -> " + resp.getLan());
    } catch (NetworkException|MarshalException exc) {
      Log.e("Cannot get response from " + LAN_INFO + " request", exc);
      throw exc;
    }

    if (resp.getStat().equals("ok"))
      return resp.getLan();
    
    Error err = resp.getErr();
    Log.e("failed " + LAN_INFO + " request: (" + err.getCode() + ") " + err.getMsg());
    throw new APIException(LAN_INFO + " request fails: " + err.getMsg(), err.getCode());
  }
  
  // ************************************************************
  // ********** ont section
  // ************************************************************

  // ************************************************************
  // ********** p910nd section
  // ************************************************************

  // ************************************************************
  // ********** ppp section
  // ************************************************************

  /**
   * Returns status and IP address for PPP connection.
   * This method is publicly available.
   * 
   * @return status and IP address for PPP connection.
   * 
   * @throws NetworkException There is an error during HTTP REST request.
   * @throws MarshalException There is an error during the REST reply unmarshalling.
   * @throws APIException     There is an error returned by 9box API.
   */
  public PPPInfo getPPPInfo() throws NetworkException, MarshalException, APIException {
    String uri = "http://" + ip + URL_API_REST + PPP_INFO + "&token=" + auth.getToken();

    Response resp = null;
    try {
      resp = getResponse(uri);
      if (Log.debug) Log.d(PPP_INFO + " -> " + resp.getPPP());
    } catch (NetworkException|MarshalException exc) {
      Log.e("Cannot get response from " + PPP_INFO + " request", exc);
      throw exc;
    }

    if (resp.getStat().equals("ok"))
      return resp.getPPP();
    
    Error err = resp.getErr();
    Log.e("failed " + PPP_INFO + " request: (" + err.getCode() + ") " + err.getMsg());
    throw new APIException(PPP_INFO + " request fails: " + err.getMsg(), err.getCode());
  }
  
  /**
   * Returns login and password for PPP connection.
   * This method is private.
   * 
   * @return login and password for PPP connection.
   * 
   * @throws NetworkException There is an error during HTTP REST request.
   * @throws MarshalException There is an error during the REST reply unmarshalling.
   * @throws APIException     There is an error returned by 9box API.
   */
  public PPPInfo getPPPCredentials() throws NetworkException, MarshalException, APIException {
    if (auth == null) {
      Log.e("failed " + PPP_GET_CREDENTIALS + " request: authentication needed.");
      throw new APIException(PPP_GET_CREDENTIALS + " request fails: authentication needed", 115);
    }

    String uri = "http://" + ip + URL_API_REST + PPP_GET_CREDENTIALS + "&token=" + auth.getToken();

    Response resp = null;
    try {
      resp = getResponse(uri);
      if (Log.debug) Log.d(PPP_GET_CREDENTIALS + " -> " + resp.getPPP());
    } catch (NetworkException|MarshalException exc) {
      Log.e("Cannot get response from " + PPP_GET_CREDENTIALS + " request", exc);
      throw exc;
    }

    if (resp.getStat().equals("ok"))
      return resp.getPPP();
    
    Error err = resp.getErr();
    Log.e("failed " + PPP_GET_CREDENTIALS + " request: (" + err.getCode() + ") " + err.getMsg());
    throw new APIException(PPP_GET_CREDENTIALS + " request fails: " + err.getMsg(), err.getCode());
  }

  // ************************************************************
  // ********** smb section
  // ************************************************************

  // ************************************************************
  // ********** system section
  // ************************************************************

  /**
   * Returns various informations about 9box.
   * This method is publicly available.
   * 
   * @return various informations about 9box.
   * 
   * @throws NetworkException There is an error during HTTP REST request.
   * @throws MarshalException There is an error during the REST reply unmarshalling.
   * @throws APIException     There is an error returned by 9box API.
   */
  public SysInfo getSystemInfo() throws NetworkException, MarshalException, APIException {
    String uri = "http://" + ip + URL_API_REST + SYS_INFO;

    Response resp = null;
    try {
      resp = getResponse(uri);
      if (Log.debug) Log.d(SYS_INFO + " -> " + resp.getSystem());
    } catch (NetworkException|MarshalException exc) {
      Log.e("Cannot get response from " + SYS_INFO + " request", exc);
      throw exc;
    }

    if (resp.getStat().equals("ok"))
      return resp.getSystem();
    
    Error err = resp.getErr();
    Log.e("failed " + SYS_INFO + " request: (" + err.getCode() + ") " + err.getMsg());
    throw new APIException(SYS_INFO + " request fails: " + err.getMsg(), err.getCode());
  }

  /**
   * Returns default WPA Key.
   * This method is private.
   * 
   * @return default WPA Key.
   * 
   * @throws NetworkException There is an error during HTTP REST request.
   * @throws MarshalException There is an error during the REST reply unmarshalling.
   * @throws APIException     There is an error returned by 9box API.
   */
  public SysInfo getWPAKey() throws NetworkException, MarshalException, APIException {
    if (auth == null) {
      Log.e("failed " + SYS_GET_WPAKEY + " request: authentication needed.");
      throw new APIException(SYS_GET_WPAKEY + " request fails: authentication needed", 115);
    }

    String uri = "http://" + ip + URL_API_REST + SYS_GET_WPAKEY + "&token=" + auth.getToken();
    
    Response resp = null;
    try {
      resp = getResponse(uri);
      if (Log.debug) Log.d(SYS_GET_WPAKEY + " -> " + resp.getSystem());
    } catch (NetworkException|MarshalException exc) {
      Log.e("Cannot get response from " + SYS_GET_WPAKEY + " request", exc);
      throw exc;
    }

    if (resp.getStat().equals("ok"))
      return resp.getSystem();
    
    Error err = resp.getErr();
    Log.e("failed " + SYS_GET_WPAKEY + " request: (" + err.getCode() + ") " + err.getMsg());
    throw new APIException(SYS_GET_WPAKEY + " request fails: " + err.getMsg(), err.getCode());
  }

  // ************************************************************
  // ********** voip section
  // ************************************************************

  // ************************************************************
  // ********** wan section
  // ************************************************************
  
  /**
   * Returns WAN informations: status, infra, IP address and uptime.
   * This method is publicly available since firmware 3.0.6.
   * 
   * @return WAN informations.
   * 
   * @throws NetworkException There is an error during HTTP REST request.
   * @throws MarshalException There is an error during the REST reply unmarshalling.
   * @throws APIException     There is an error returned by 9box API.
   */
  private WanInfo getWanInfo() throws NetworkException, MarshalException, APIException {
    String uri = "http://" + ip + URL_API_REST + WAN_INFO + ((auth != null)?"&token=" + auth.getToken():"");
    
    Response resp = null;
    try {
      resp = getResponse(uri);
      if (Log.debug) Log.d(WAN_INFO + " -> " + resp.getWan());
    } catch (NetworkException|MarshalException exc) {
      Log.e("Cannot get response from " + WAN_INFO + " request", exc);
      throw exc;
    }

    if (resp.getStat().equals("ok"))
      return resp.getWan();
    
    Error err = resp.getErr();
    Log.e("failed " + WAN_INFO + " request: (" + err.getCode() + ") " + err.getMsg());
    throw new APIException(WAN_INFO + " request fails: " + err.getMsg(), err.getCode());
  }

  // ************************************************************
  // ********** wlan section
  // ************************************************************
  
  /**
   * Returns WLAN informations: status, channel, mode, etc.
   * This method is private.
   * 
   * @return WLAN informations.
   * 
   * @throws NetworkException There is an error during HTTP REST request.
   * @throws MarshalException There is an error during the REST reply unmarshalling.
   * @throws APIException     There is an error returned by 9box API.
   */
  public WLanInfo getWlanInfo() throws NetworkException, MarshalException, APIException {
    if (auth == null) {
      Log.e("failed " + WLAN_INFO + " request: authentication needed.");
      throw new APIException(WLAN_INFO + " request fails: authentication needed", 115);
    }

    String uri = "http://" + ip + URL_API_REST + WLAN_INFO + "&token=" + auth.getToken();
    
    Response resp = null;
    try {
      resp = getResponse(uri);
      if (Log.debug) Log.d(WLAN_INFO + " -> " + resp.getWlan());
    } catch (NetworkException|MarshalException exc) {
      Log.e("Cannot get response from " + WLAN_INFO + " request", exc);
      throw exc;
    }

    if (resp.getStat().equals("ok"))
      return resp.getWlan();
    
    Error err = resp.getErr();
    Log.e("failed " + WLAN_INFO + " request: (" + err.getCode() + ") " + err.getMsg());
    throw new APIException(WLAN_INFO + " request fails: " + err.getMsg(), err.getCode());
  }
  
  /**
   * Sets the WiFi channel.
   * This method is private.
   * 
   * @param channel the requested WiFi channel.

   * @throws NetworkException There is an error during HTTP REST request.
   * @throws MarshalException There is an error during the REST reply unmarshalling.
   * @throws APIException     There is an error returned by 9box API.
   */
  public void setWlanChannel(String channel) throws NetworkException, MarshalException, APIException {
    if (auth == null) {
      Log.e("failed " + WLAN_SET_CHANNEL + " request: authentication needed.");
      throw new APIException(WLAN_SET_CHANNEL + " request fails: authentication needed", 115);
    }

    String uri = "http://" + ip + URL_API_REST + WLAN_SET_CHANNEL + "&channel=" + channel + "&token=" + auth.getToken();
    
    Response resp = null;
    try {
      resp = postRequest(uri);
      if (Log.debug) Log.d(WLAN_SET_CHANNEL + " -> " + resp.getStat());
    } catch (NetworkException|MarshalException exc) {
      Log.e("Cannot get response from " + WLAN_SET_CHANNEL + " request", exc);
      throw exc;
    }

    if (resp.getStat().equals("ok")) return;
    
    Error err = resp.getErr();
    Log.e("failed " + WLAN_SET_CHANNEL + " request: (" + err.getCode() + ") " + err.getMsg());
    throw new APIException(WLAN_SET_CHANNEL + " request fails: " + err.getMsg(), err.getCode());
  }
  
  /**
   * Set the WiFi radio mode (for NB5/NB6 mode = 11n|11ng|11g, for NB4/CIBOX mode = 11b|11g|auto).
   * This method is private.
   * 
   * @param mode the WiFi radio mode.
   * 
   * @throws NetworkException There is an error during HTTP REST request.
   * @throws MarshalException There is an error during the REST reply unmarshalling.
   * @throws APIException     There is an error returned by 9box API.
   */
  public void setWlanMode(String mode) throws NetworkException, MarshalException, APIException {
    if (auth == null) {
      Log.e("failed " + WLAN_SET_MODE + " request: authentication needed.");
      throw new APIException(WLAN_SET_MODE + " request fails: authentication needed", 115);
    }

    String uri = "http://" + ip + URL_API_REST + WLAN_SET_MODE + "&mode=" + mode  + "&token=" + auth.getToken();
    
    Response resp = null;
    try {
      resp = postRequest(uri);
      if (Log.debug) Log.d(WLAN_SET_MODE + " -> " + resp.getStat());
    } catch (NetworkException|MarshalException exc) {
      Log.e("Cannot get response from " + WLAN_SET_MODE + " request", exc);
      throw exc;
    }

    if (resp.getStat().equals("ok")) return;
    
    Error err = resp.getErr();
    Log.e("failed " + WLAN_SET_MODE + " request: (" + err.getCode() + ") " + err.getMsg());
    throw new APIException(WLAN_SET_MODE + " request fails: " + err.getMsg(), err.getCode());
  }
  
  // ************************************************************
  // ********** For test purpose
  // ************************************************************
  
  /**
   * Allows to test the wrapper through several requests.
   * It needs to be call with IP address of the box (192.168.1.1), the administrator login (admin) and
   * the password of the 9box.
   * 
   * @param args  IP address of the box, the administrator login and password of the 9box.
   */
  public static void main(String[] args) throws Exception {
    NBWrapper test = new NBWrapper(args[0], args[1], args[2]);
    
    // Test a public method
    test.getSystemInfo();
    try {
      // Test a private method without authentication
      test.getPPPCredentials();
    } catch (Exception exc) {
      exc.printStackTrace();
    }
    
    // auth
    test.authenticate();
    // backup3g
    // ddns
    //dsl
    test.getDslInfo();
    // firewall
    // hotspot
    // lan
    test.getLanInfo();
    test.getDnsHostList();
    test.getHostsList();
    // ont
    // p910nd
    // ppp
    test.getPPPInfo();
    test.getPPPCredentials();
    // smb
    // system
    test.getSystemInfo();
    test.getWPAKey();
    // voip
    // wan
    test.getWanInfo();
    // wlan
    test.getWlanInfo();
//    test.setWlanMode("11g");
//    test.setWlanChannel("13");
//    test.getWlanInfo();
  }
}